package megrez.boot.autoconfigure.okhttp;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.time.Duration;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.HostnameVerifier;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import lombok.extern.slf4j.Slf4j;
import okhttp3.Cache;
import okhttp3.CertificatePinner;
import okhttp3.ConnectionPool;
import okhttp3.CookieJar;
import okhttp3.Dns;
import okhttp3.EventListener;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;

/**
 * 自动配置
 * 
 * @author Lucky Yang
 * @since 2.6.9
 */
@Slf4j
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ OkHttpClient.class })
@ConditionalOnProperty(value = "okhttp.enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(OkHttpProperties.class)
public class OkHttp3AutoConfiguration {
    private final OkHttpProperties okHttpProperties;
    private final ObjectProvider<Interceptor> applicationInterceptors;
    private final ObjectProvider<Interceptor> networkInterceptors;

    public OkHttp3AutoConfiguration(OkHttpProperties okHttpProperties,
            @ApplicationInterceptor ObjectProvider<Interceptor> applicationInterceptors,
            @NetworkInterceptor ObjectProvider<Interceptor> networkInterceptors) {
        this.okHttpProperties = okHttpProperties;
        this.applicationInterceptors = applicationInterceptors;
        this.networkInterceptors = networkInterceptors;
    }

    @Bean
    @ConditionalOnMissingBean
    public ConnectionPool okHttp3ConnectionPool() {
        int maxIdleConnections = okHttpProperties.getConnectionPool().getMaxIdleConnections();
        Duration keepAliveDuration = okHttpProperties.getConnectionPool().getKeepAliveDuration();
        return new ConnectionPool(maxIdleConnections, keepAliveDuration.toNanos(), TimeUnit.NANOSECONDS);
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(value = "okhttp.cache.enabled", havingValue = "true")
    public Cache okHttp3Cache() throws IOException {
        log.info("Initializing OkHttpClient cache");
        File directory = okHttpProperties.getCache().getDirectory();
        if (directory == null) {
            directory = Files.createTempDirectory("okhttp-cache").toFile();
        }
        return new Cache(directory, okHttpProperties.getCache().getMaxSize().toBytes());
    }

    @Bean
    @ConditionalOnMissingBean
    public OkHttpClient okHttp3Client(
            ObjectProvider<Cache> cache,
            ObjectProvider<CookieJar> cookieJar,
            ObjectProvider<Dns> dns,
            ObjectProvider<HostnameVerifier> hostnameVerifier,
            ObjectProvider<CertificatePinner> certificatePinner,
            ConnectionPool connectionPool,
            ObjectProvider<EventListener> eventListener,
            ObjectProvider<OkHttp3Configurer> configurers) {
        log.info("Initializing OkHttpClient [{}]", okHttpProperties);
        OkHttpClient.Builder builder = new OkHttpClient.Builder();

        cache.ifUnique(builder::cache);

        eventListener.ifUnique(builder::eventListener);

        builder.connectTimeout(okHttpProperties.getConnectTimeout().toMillis(), TimeUnit.MILLISECONDS);
        builder.readTimeout(okHttpProperties.getReadTimeout().toMillis(), TimeUnit.MILLISECONDS);
        builder.writeTimeout(okHttpProperties.getWriteTimeout().toMillis(), TimeUnit.MILLISECONDS);
        builder.pingInterval(okHttpProperties.getPingInterval().toMillis(), TimeUnit.MILLISECONDS);

        cookieJar.ifUnique(builder::cookieJar);

        dns.ifUnique(builder::dns);

        hostnameVerifier.ifUnique(builder::hostnameVerifier);
        certificatePinner.ifUnique(builder::certificatePinner);

        builder.connectionPool(connectionPool);

        builder.followRedirects(okHttpProperties.isFollowRedirects());
        builder.followSslRedirects(okHttpProperties.isFollowSslRedirects());
        builder.retryOnConnectionFailure(okHttpProperties.isRetryOnConnectionFailure());

        Set<String> names = new HashSet<>();
        applicationInterceptors.forEach(interceptor -> {
            builder.addInterceptor(interceptor);
            names.add(interceptor.getClass().getName());
        });

        networkInterceptors.forEach(interceptor -> {
            builder.addNetworkInterceptor(interceptor);
            names.add(interceptor.getClass().getName());
        });

        if (!names.isEmpty()) {
            log.info("OkHttp interceptors found: {}", names);
        }

        configurers.forEach(configurer -> configurer.configure(builder));

        return builder.build();
    }
}
